/*
 * mail_mgr.cpp
 *
 *  Created on: Jan 20, 2014
 *      Author: killerg
 */

#include "../server_deps.h"
#include "mail_mgr.h"
#include "player_mgr.h"
#include "player_data.h"

#include "../server.h"
#include "../server_cfg.h"
#include "../game_logic/game_cfg.h"
#include "../server_console.h"

#include "share/db_mgr.h"
#include "share/util.h"
#include "share/co.pb.h"
#include "share/clock.h"


void mailbox_t::init(const Json::Value &json) {
    const Json::Value &system_json = json["system"];
    for (auto itr = system_json.begin(); itr != system_json.end(); ++itr) {
        const Json::Value &mail_json = (*itr);

        mail_t* mail = new mail_t;
        mail->mailid = mail_json.get("mailid", -1).asInt();
        mail->sender = mail_json.get("sender", -1).asInt64();
        mail->date = mail_json.get("date", 0).asInt();
        mail->content = mail_json.get("content", "").asCString();
        mail->rewardid = mail_json.get("rewardid", -1).asInt();
        mail->rewardpct = mail_json.get("rewardpct", 0.0f).asDouble();
        mail->readed = mail_json.get("readed", true).asBool();
        mail->rewarded = mail_json.get("rewarded", true).asBool();
        mail->type = mail_json.get("type", cs::EMT_Person).asInt();

        system.set(mail->mailid, mail);
    }

    const Json::Value &person_json = json["person"];
    for (auto itr = person_json.begin(); itr != person_json.end(); ++itr) {
        const Json::Value &mail_json = (*itr);

        mail_t* mail = new mail_t;
        mail->mailid = mail_json.get("mailid", -1).asInt();
        mail->sender = mail_json.get("sender", -1).asInt64();
        mail->date = mail_json.get("date", 0).asInt();
        mail->content = mail_json.get("content", "").asCString();
        mail->rewardid = mail_json.get("rewardid", -1).asInt();
        mail->rewardpct = mail_json.get("rewardpct", 0.0f).asDouble();
        mail->readed = mail_json.get("readed", true).asBool();
        mail->rewarded = mail_json.get("rewarded", true).asBool();
        mail->type = mail_json.get("type", cs::EMT_Person).asInt();

        person.set(mail->mailid, mail);
    }

    new_system = json.get("new_system", 0).asInt();
    new_person = json.get("new_person", 0).asInt();
    last_system = json.get("last_system", -1).asInt();
    last_person = json.get("last_person", -1).asInt();
    boxid = json.get("boxid", -1).asInt64() + json.get("serverid", -1).asInt64() * co::MaxAccountNum;
    account = json.get("account","").asCString();
    playername = json.get("playername", "").asCString();

    valid_mailid = json.get("valid_mailid", 0).asInt();
}
void mailbox_t::save(Json::Value &json) {
    json["objtype"] = "mailbox";
    json["serverid"] = g_cfg->ServerId;
    json["boxid"] = Json::Int64(boxid % co::MaxAccountNum);
    json["account"] = account;
    json["playername"] = playername;

    Json::Value mail_json;

    Json::Value &system_json = json["system"];
    for (const auto &itr : system) {
        mail_json["mailid"] = itr.second->mailid;
        mail_json["sender"] = Json::Int64(itr.second->sender);
        mail_json["date"] = itr.second->date;
        mail_json["content"] = itr.second->content;
        mail_json["rewardid"] = itr.second->rewardid;
        mail_json["rewardpct"] = itr.second->rewardpct;
        mail_json["readed"] = itr.second->readed;
        mail_json["rewarded"] = itr.second->rewarded ;
        mail_json["type"] = itr.second->type;

        system_json.append(mail_json);
    }

    Json::Value &person_json = json["person"];
    for (const auto &itr : person) {
        mail_json["mailid"] = itr.second->mailid;
        mail_json["sender"] = Json::Int64(itr.second->sender);
        mail_json["date"] = itr.second->date;
        mail_json["content"] = itr.second->content;
        mail_json["rewardid"] = itr.second->rewardid;
        mail_json["rewardpct"] = itr.second->rewardpct;
        mail_json["readed"] = itr.second->readed;
        mail_json["rewarded"] = itr.second->rewarded ;
        mail_json["type"] = itr.second->type;

        person_json.append(mail_json);
    }

    json["new_system"] = new_system;
    json["new_person"] = new_person;
    json["last_system"] = last_system;
    json["last_person"] = last_person;

    json["valid_mailid"] = valid_mailid;
}

void mailbox_t::destroy() {
    for (const auto &itr : person)
        delete(itr.second);
    for (const auto &itr : system)
        delete(itr.second);
    person.clear();
    system.clear();
}

void mailbox_t::add_queue(bool chg) {
    if (chg)
        chg_stamp = g_clock->tt_now;

    if (state != Unchged)
        return;

    state = Queuing;
    g_mail->save_queue.push(this);
}

void mailbox_t::post_save(function<void(bool)> cb) {
	if (!g_dbf->ok()) {
		if (cb)
			cb(false);
		return;
	}
	Json::Value json;
	save(json);
	bool res = g_dbf->set(
			fmt_cstr("mailbox:%d:%lld", g_cfg->ServerId, boxid % co::MaxAccountNum),
			json,
			[cb](db_error_t err){
                if (cb)
                    cb(err == DBE_Success);
			});
	if (!res) {
	    if (cb)
            cb(false);
	}
}

void mailmgr_t::box_load(int64_t boxid, mailbox_load_cb_t cb) {
	if (!g_dbf->ok()) {
		if (cb)
			cb(DBE_TryMoreLater, NULL);
		return;
	}
    auto mailbox = boxes.get(boxid);
    if (NULL != mailbox) {
        cb(DBE_Success, mailbox);
        return;
    }

    auto itr = loading.find(boxid);
    if (itr != loading.end()) {
        itr->second.push_back(cb);
        return;
    }

    bool res = g_dbf->get(
            fmt_cstr("mailbox:%d:%lld", g_cfg->ServerId, boxid % co::MaxAccountNum),
            [this, boxid](db_error_t err, const Json::Value &json){
                mailbox_t* mailbox = NULL;
                if (DBE_Success == err) {
                    mailbox = new mailbox_t;
                    mailbox->init(json);
                    boxes.set(boxid, mailbox);
                }

                auto itr = loading.find(boxid);
                if (itr != loading.end()) {
                    for (auto itr2 : itr->second)
                        itr2(err, mailbox);

                    loading.erase(itr);
                }
            });
    if (!res) {
        cb(DBE_TryMoreLater, NULL);
    } else {
        itr = loading.insert(make_pair(boxid, list<mailbox_load_cb_t>())).first;
        itr->second.push_back(cb);
    }
}
void mailmgr_t::box_create(int64_t boxid, const string &account, const string &playername, mailbox_create_cb_t cb) {
	if (!g_dbf->ok()) {
		if (cb)
			cb(DBE_TryMoreLater);
		return;
	}

    auto mailbox = boxes.get(boxid);
    if (NULL != mailbox) {
        cb(DBE_Exist);
        return;
    }

    auto itr = creating.find(boxid);
    if (itr != creating.end()) {
        cb(DBE_TryMoreLater);
        return;
    }

    Json::Value json;
    mailbox_t box;
    box.boxid = boxid;
    box.account = account;
    box.playername = playername;
    box.save(json);
    bool res = g_dbf->add(
            fmt_cstr("mailbox:%d:%lld", g_cfg->ServerId, boxid % co::MaxAccountNum),
            json,
            [this, boxid](db_error_t err){
                auto itr = creating.find(boxid);
                if (itr != creating.end()) {
                    itr->second(err);

                    creating.erase(itr);
                }
            });
    if (!res) {
        cb(DBE_TryMoreLater);
    } else {
        creating.insert(make_pair(boxid, cb));
    }
}

void mailmgr_t::send_system(
        int64_t boxid,
        cs::EMailType type,
        const string &content,
        int32_t rewardid,
        double rewardpct,
        function<void(bool)> cb)
{
    if (type == cs::EMT_Person) {
        if (cb)
            cb(false);
        return;
    }

    mail_t newmail;
    newmail.mailid = -1;
    newmail.sender = -1;
    newmail.date = g_clock->tt_now;
    newmail.content = content;
    newmail.rewardid = rewardid;
    newmail.rewardpct = rewardpct;
    newmail.readed = false;
    newmail.rewarded = false;
    newmail.type = type;
    box_load(boxid, [boxid, this, newmail, cb](db_error_t err, mailbox_t* box){
        if (err != DBE_Success || box == NULL) {
            if (cb)
                cb(false);
            return;
        }

        if (box->system.size() == 50) {
            auto itr = box->system.begin();
            if (itr->first > box->last_system) {
                --box->new_system;
            }
            delete itr->second;
            box->system.erase(itr);
        }

        mail_t* mail = new mail_t;
        *mail = newmail;
        mail->mailid = box->valid_mailid++;
        box->system.set(mail->mailid, mail);
        ++box->new_system;
        box->add_queue(true);
        if (cb)
            cb(true);

        auto player = g_playermgr->get_player(box->boxid);
        if (player) {
            player->att(cs::Pl4n_SystemMailNew, box->new_system);
        }
    });
}

void mailmgr_t::send_person(
        int64_t boxid,
        int64_t sender,
        const string &content,
        function<void(bool)> cb)
{
    mail_t newmail;
    newmail.mailid = -1;
    newmail.sender = sender;
    newmail.date = g_clock->tt_now;
    newmail.content = content;
    newmail.rewardid = -1;
    newmail.rewardpct = -1;
    newmail.readed = false;
    newmail.rewarded = false;
    newmail.type = cs::EMT_Person;
    box_load(boxid, [this, newmail, cb](db_error_t err, mailbox_t* box){
        if (err != DBE_Success || box == NULL) {
            if (cb)
                cb(false);
            return;
        }

        if (box->person.size() == 50) {
            auto itr = box->person.begin();
            if (itr->first > box->last_person) {
                --box->new_person;
            }

            delete itr->second;
            box->person.erase(itr);
        }

        mail_t* mail = new mail_t;
        *mail = newmail;
        mail->mailid = box->valid_mailid++;
        box->person.set(mail->mailid, mail);
        ++box->new_person;
        box->add_queue(true);
        if (cb)
            cb(true);

        auto player = g_playermgr->get_player(box->boxid);
        if (player) {
            player->att(cs::Pl4n_PersonMailNew, box->new_person);
        }
    });
}


bool mailmgr_t::init() {

	g_console->watch_n("mailbox_loaded", [this]()->int{ return boxes.size(); });
    g_console->watch_n("mailbox_loading", [this]()->int{ return loading.size(); });
    g_console->watch_n("mailbox_creating", [this]()->int{ return creating.size(); });
    g_console->watch_n("mailbox_save_queue", [this]()->int{ return save_queue.size(); });
    g_console->watch_n("mailbox_saving", [this]()->int{ return saving; });

    return true;
}

void mailmgr_t::destroy() {
	if (!g_dbf->ok())
		g_dbf->repair();
    update_save_queue();
    g_dbf->wait();

	boxes.for_each([this](int64_t playerid, mailbox_t* &box){
	    if (box->state != mailbox_t::Unchged) {
	        Json::Value json;
	        box->save(json);
	        UTIL_LOG(E, "mailbox:%d not saved content is %s", box->boxid, to_cstr(json));
	    }
        box->destroy();
        delete box;
	});
	boxes.clear();
}

void mailmgr_t::update_save_queue(int num) {
    if (num < 0 || num > save_queue.size())
        num = save_queue.size();

    for (int i=0; i<num; ++i) {
        auto box = save_queue.front();
        save_queue.pop();

        int64_t boxid = box->boxid;
        int32_t sav_stamp = g_clock->tt_now;

        box->state = mailbox_t::Saving;
        ++saving;
        box->post_save([boxid, sav_stamp](bool ok){
        	--g_mail->saving;
            auto box = g_mail->boxes.get(boxid);
            if (NULL == box) {
            	UTIL_LOG(E, "find not box:%lld when saved ", boxid);
            	return;
            }
            box->state = mailbox_t::Unchged;
            if (ok) {
                box->sav_stamp = sav_stamp;

                if (box->chg_stamp > box->sav_stamp)
                    box->add_queue(false);
            } else {
                box->add_queue(false);
            }

        });
    }
}


void mailmgr_t::update() {
	if (g_svr.status == co::ESS_Shuting)
		update_save_queue(50);
	else
		update_save_queue(5);

    if (!g_clock->sec_jump && (g_clock->tm_now.tm_sec % 3 == 0))
        return;

    int boxes_num = boxes.size();
    if (boxes_num < g_game_cfg->MaxMailBoxNumOnline)
        return;

    struct comp {
        bool operator() (const mailbox_t* lhs, const mailbox_t* rhs) {
            return lhs->chg_stamp > rhs->chg_stamp;
        }
    };

    priority_queue<mailbox_t*, vector<mailbox_t*>, comp> queue;
    for (auto itr : boxes) {
        if (mailbox_t::Unchged == itr.second->state && !itr.second->player_online)
            queue.push(itr.second);
    }

    int gc_num = boxes_num - g_game_cfg->MaxMailBoxNumOnline + 2000;
    for (int i=0; i<gc_num && !queue.empty(); ++i) {
        mailbox_t* to_gc = queue.top();
        queue.pop();

        boxes.erase(to_gc->boxid);
        to_gc->destroy();
        delete to_gc;
    }
}
